Add regression tests for null !== $x = expr() assignment-in-condition pattern with &ᘴ
Add regression tests for null !== $x = expr() assignment-in-condition pattern with &ᘴphpstan-bot wants to merge 1 commit into
null !== $x = expr() assignment-in-condition pattern with &ᘴConversation
…on pattern with `&&` - Add NSRT test verifying type narrowing works correctly for the parenthesized pattern: (null !== $x = expr()) && use($x) - Add DefinedVariableRule test documenting that the unparenthesized version correctly reports "Undefined variable" (due to PHP operator precedence, && binds tighter than =) - Cover variations: logical 'and'/'or' (lower precedence), chained assignments, false !== pattern, while loops, || with === null The reported code `null !== $x = expr() && use($x)` parses as `null !== ($x = (expr() && use($x)))` due to && having higher precedence than =. The variable $x is genuinely undefined inside the BooleanAnd's right side. PHPStan correctly reports this error. The correctly parenthesized version `(null !== $x = expr()) && use($x)` already works correctly in PHPStan - variable is defined and narrowed to non-null in the right side of &&. Closes phpstan/phpstan#4662
VincentLanglet
left a comment
There was a problem hiding this comment.
The following code works fine in PHP
class Foo
{
/**
* @var \DateTimeImmutable|null
*/
private $programStartDate;
public function getProgramStartDate(): ?\DateTimeImmutable
{
return $this->programStartDate;
}
}
function test(Foo $foo): void
{
$now = new \DateTimeImmutable('now');
if (
null !== $programStartDate = $foo->getProgramStartDate()
&& $now > $programStartDate
) {
echo 'ok';
}
}
test(new Foo());
Do you have a proof that $programStartDate is not defined ?
|
The token doesn't have permission to comment on the PR. No code changes are needed here — the PR is correct and the tests pass. Let me summarize the findings. Summary of the review response: The reviewer's question "Do you have a proof that
No code changes are needed. The PR correctly documents PHPStan's behavior. Tests pass and |
Summary
The issue reports that
null !== $programStartDate = $foo->getProgramStartDate() && $now > $programStartDateproduces "Undefined variable: $programStartDate". After thorough analysis, this is correct behavior — the code has a genuine PHP bug due to operator precedence.The
&&operator has higher precedence than=(assignment), so the expression parses as:The variable
$programStartDateis used inside the right side of&&, which is part of the assignment's RHS — meaning$programStartDateis genuinely undefined at that point in PHP execution.Changes
tests/PHPStan/Analyser/nsrt/bug-4662.php) verifying that the correctly parenthesized patterns all work:(null !== $x = expr()) && use($x)— type narrowing works($x = expr()) !== null && use($x)— reversed comparisonnull !== $x = expr() and use($x)—andhas lower precedence than=false !==pattern,||with=== null, while loops, nested assignmentstests/PHPStan/Rules/Variables/data/bug-4662.php) documenting:andkeyword version produces no errorRoot cause
Not a bug. PHP operator precedence causes
&&to bind tighter than=, so:null !== $x = expr() && use($x)→null !== ($x = (expr() && use($x)))—$xis undefined inuse($x)(null !== $x = expr()) && use($x)→ correctly defines$xfirst, then uses itPHPStan correctly analyzes both forms according to PHP's actual parsing rules.
Workarounds for users
(null !== $x = expr()) && use($x)andkeyword (lower precedence):null !== $x = expr() and use($x)$x = expr(); if (null !== $x && use($x)) { ... }Test
Fixes phpstan/phpstan#4662